package net.jalbum.filters;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

import net.jalbum.filterManager.FilterProperties.FilterCategory;

import se.datadosen.jalbum.ModifiesSize;
import se.datadosen.jalbum.Msg;

public class StraightenFilterPlugin extends BasicFilter implements ModifiesSize {

	/**
	 * 
	 */
	private static final long serialVersionUID = 3126311644370597656L;
	protected StraightenControl straightenControl;

	protected Color color = new Color(255, 255, 255);
	protected double angle = 0;
	protected double angle_out = 0;
	protected boolean random = false;
	protected boolean antialias = true;
	protected boolean user_rotating = false;
	protected boolean showGrid = true;
	protected int orientationWidth;
	protected int orientationHeight;
	protected int startX;
	protected int startY;
	protected int currentX;
	protected int currentY;
	public static final int RESIZE_CROPPING = 0;
	public static final int RESIZE_KEEP_SIZE = 1;
	public static final int RESIZE_ENLARGE = 2;
	protected int resizeOption = StraightenFilterPlugin.RESIZE_CROPPING;

	// Settings
	protected int initalPanelWidth = 400;
	protected int initalPanelHeight = 400;
	protected final int guidelinesVertical = 16;
	protected final int guidelinesHorizontal = 16;

	public StraightenFilterPlugin() {
		this.name = Msg.getString(this, "filters.straighten.name");
		this.shortName = Msg.getString(this, "filters.straighten.shortName");
		this.icon = new ImageIcon(this.getClass().getResource("res/straighten.png"));
		this.author = "David Fichtmueller";
		this.version = "1.0";
		this.description = Msg.getString(this, "filters.straighten.description");
		this.category = FilterCategory.BASIC;
		this.prescale = true;
		this.postscale = false;

		this.straightenControl = new StraightenControl(this);
	}

	@Override
	public JPanel getControls() {
		return this.straightenControl;
	}

	@Override
	public BufferedImage renderImage(final BufferedImage bi) {
		BufferedImage out;
		this.angle_out = this.checkAngle(this.angle_out);
		final int w = bi.getWidth();
		final int h = bi.getHeight();
		final double segment1Width = Math.abs(h * Math.sin(Math.toRadians(this.angle_out)));
		final double segment2Width = Math.abs(w * Math.cos(Math.toRadians(this.angle_out)));
		final double segment1Height = Math.abs(h * Math.cos(Math.toRadians(this.angle_out)));
		final double segment2Height = Math.abs(w * Math.sin(Math.toRadians(this.angle_out)));
		final int newW = (int) (segment1Width + segment2Width);
		final int newH = (int) (segment1Height + segment2Height);

		if (this.resizeOption == StraightenFilterPlugin.RESIZE_CROPPING) {

			int smallerSide = 0;
			int largerSide = 0;
			// the larger side defines the size of cropped image
			if (w < h) {
				smallerSide = bi.getWidth();
				largerSide = bi.getHeight();
			} else {
				smallerSide = bi.getHeight();
				largerSide = bi.getWidth();
			}

			final double absAngle_out = Math.abs(this.angle_out);
			final double angleA = Math.toRadians(absAngle_out - ((int) (absAngle_out - 1) / 90) * 2 * (absAngle_out - 90));
			final double angleB = Math.atan(largerSide / (double) smallerSide);
			final double angleC = angleB - angleA;

			final double diagonal = smallerSide / Math.cos(angleC);

			double unroundedWidth = 0;
			double unroundedHeight = 0;
			if (w < h) {
				unroundedWidth = diagonal * Math.cos(angleB);
				unroundedHeight = diagonal * Math.sin(angleB);
			} else {
				unroundedWidth = diagonal * Math.sin(angleB);
				unroundedHeight = diagonal * Math.cos(angleB);
			}

			// calculate width, height and position of the to be cut rectangle
			final int cuttingWidth = (int) (unroundedWidth);
			final int cuttingHeight = (int) (unroundedHeight);

			final int positionX = (int) (this.orientationWidth * 0.5 - unroundedWidth / 2);
			final int positionY = (int) (this.orientationHeight * 0.5 - unroundedHeight / 2);

			if (this.user_rotating) {
				// create new image
				out = new BufferedImage(this.orientationWidth, this.orientationHeight, BufferedImage.TYPE_INT_RGB);
				final Graphics2D g = out.createGraphics();
				g.setColor(this.color);
				g.fillRect(0, 0, this.orientationWidth, this.orientationHeight);
				g.rotate(Math.toRadians(this.angle_out), this.orientationWidth / 2, this.orientationHeight / 2);
				g.drawImage(bi, (this.orientationWidth - w) / 2, (this.orientationHeight - h) / 2, null);
				g.dispose();

				final Graphics g2 = out.getGraphics();
				g2.setColor(Color.DARK_GRAY);
				// draw a rectangle for the cropped area, position is adjusted, since it is displayed in a different frame
				g2.drawRect(positionX, positionY, cuttingWidth, cuttingHeight);

				g.fillOval(newW - (int) segment1Width, newH - (int) segment1Height, 3, 3);
			} else {
				// create new image
				out = new BufferedImage(cuttingWidth, cuttingHeight, BufferedImage.TYPE_INT_RGB);
				final Graphics2D g = out.createGraphics();
				if (this.antialias) {
					g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				}
				g.setColor(this.color);
				g.fillRect(0, 0, cuttingWidth, cuttingHeight);

				g.rotate(Math.toRadians(this.angle_out), cuttingWidth / 2, cuttingHeight / 2);
				g.drawImage(bi, (cuttingWidth - w) / 2, (cuttingHeight - h) / 2, null);
				g.dispose();
			}
		} else if (this.resizeOption == StraightenFilterPlugin.RESIZE_KEEP_SIZE) {
			// create new image
			out = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
			final Graphics2D g = out.createGraphics();
			if (this.antialias) {
				g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
			}
			g.setColor(this.color);
			g.fillRect(0, 0, w, h);

			g.rotate(Math.toRadians(this.angle_out), w / 2, h / 2);
			// scale the image back to the original size
			// g.scale(w/(double)cuttingWidth, h/(double)cuttingHeight);

			g.drawImage(bi, 0, 0, null);
			g.dispose();
		} else {
			// RESZIE_ENLARGE or any other, if an error happend
			if (this.user_rotating) {
				// create new image with the dimensions of the previous one
				out = new BufferedImage(this.orientationWidth, this.orientationHeight, BufferedImage.TYPE_INT_RGB);
				final Graphics2D g = out.createGraphics();
				g.setColor(this.color);
				g.fillRect(0, 0, this.orientationWidth, this.orientationHeight);
				g.rotate(Math.toRadians(this.angle_out), this.orientationWidth / 2, this.orientationHeight / 2);
				g.drawImage(bi, (this.orientationWidth - w) / 2, (this.orientationHeight - h) / 2, null);
				g.dispose();
			} else {
				// create new image
				out = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB);
				final Graphics2D g = out.createGraphics();
				if (this.antialias) {
					g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				}
				g.setColor(this.color);
				g.fillRect(0, 0, newW, newH);

				g.rotate(Math.toRadians(this.angle_out), newW / 2, newH / 2);
				g.drawImage(bi, (newW - w) / 2, (newH - h) / 2, null);
				g.dispose();

			}

		}

		return out;
	}

	@Override
	public void paint(final Graphics g) {
		// draw Guidelines
		if (this.showGrid) {
			g.setColor(new Color(200, 200, 200, 150));
			for (int i = 0; i < this.guidelinesVertical; i++) {
				g.drawLine(i * this.outputImage.getWidth() / this.guidelinesVertical, 0, i * this.outputImage.getWidth() / this.guidelinesVertical, this.outputImage.getHeight());
			}
			for (int i = 0; i < this.guidelinesHorizontal; i++) {
				g.drawLine(0, i * this.outputImage.getHeight() / this.guidelinesHorizontal, this.outputImage.getWidth(), i * this.outputImage.getHeight() / this.guidelinesHorizontal);
			}
		}
		if (this.user_rotating) {
			g.setColor(new Color(64, 64, 64, 255));
			g.drawLine(this.startX, this.startY, this.outputImage.getWidth() / 2, this.outputImage.getHeight() / 2);
			g.drawLine(this.currentX, this.currentY, this.outputImage.getWidth() / 2, this.outputImage.getHeight() / 2);
		}
	}

	public void setColor(final Color color) {
		this.color = color;
	}

	public void adjustAngle(final double angle) {
		this.angle = angle;
		this.angle_out = this.checkAngle(angle);
		this.renderPreview();
	}

	public void setAngle(final double angle) {
		this.angle = angle;
	}

	public void adjustRandom(final boolean random) {
		// currently deactivated in the straightenControlPanel
		this.random = random;
		if (random) {
			this.angle = Math.random() * Math.abs(this.angle) * 2 - Math.abs(this.angle);
		}
		this.straightenControl.updateAngle(this.angle);
		this.angle_out = this.checkAngle(this.angle);
		this.renderPreview();
	}

	public void setRandom(final boolean random) {
		this.random = random;
	}

	public void setResizeOption(final int resizeOption) {
		this.resizeOption = resizeOption;
	}

	public void setShowGrid(final boolean showGrid) {
		this.showGrid = showGrid;
	}

	@Override
	public void mouseEntered(final MouseEvent arg0) {
//		this.filterManager.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
		this.filterManager.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	}

	@Override
	public void mouseExited(final MouseEvent arg0) {
		this.filterManager.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	@Override
	public void mousePressed(final MouseEvent event) {
		this.antialias = false;
		this.user_rotating = true;
		this.orientationWidth = this.outputImage.getWidth();
		this.orientationHeight = this.outputImage.getHeight();
		this.startX = event.getX();
		this.startY = event.getY();
		// to avoid problems with the calculation of the angle, since it uses the tangent
		if (this.startX - this.outputImage.getWidth() / 2 == 0) {
			this.startX++;
		}
		if (this.startY - this.outputImage.getHeight() / 2 == 0) {
			this.startY++;
		}
		this.currentX = event.getX();
		this.currentY = event.getY();
		this.renderPreview();
	}

	@Override
	public void mouseReleased(final MouseEvent event) {
		this.antialias = true;
		this.user_rotating = false;
		this.currentX = event.getX();
		this.currentY = event.getY();
		this.calculateAngle();
		this.angle = this.angle_out;
		this.renderPreview();
	}

	@Override
	public void mouseDragged(final MouseEvent event) {
		this.currentX = event.getX();
		this.currentY = event.getY();
		this.calculateAngle();
		this.renderPreview();
	}

	protected double checkAngle(double angle) {
		// only returns angles between -180° and 180°
		angle = (angle % 360 + 360 + 180) % 360 - 180;
		return angle;
	}

	protected void calculateAngle() {
		// to avoid problems with the calculation of the angle, since it uses the cosine and tangens
		if (this.currentX - this.outputImage.getWidth() / 2 == 0) {
			this.currentX++;
		}
		if (this.currentY - this.outputImage.getHeight() / 2 == 0) {
			this.currentY++;
		}

		final int x1 = this.startX - this.orientationWidth / 2;
		final int y1 = this.startY - this.orientationHeight / 2;
		final int x2 = this.currentX - this.orientationWidth / 2;
		final int y2 = this.currentY - this.orientationHeight / 2;
		double angleChanged = Math.toDegrees(Math.acos((x1 * x2 + y1 * y2) / (Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2))));

		final double m1 = (this.startY - this.orientationHeight / 2) / (double) (this.startX - this.orientationWidth / 2);
		final double m2 = (this.currentY - this.orientationHeight / 2) / (double) (this.currentX - this.orientationWidth / 2);
		final double angleChanged2 = Math.toDegrees(Math.atan((m2 - m1) / (1 + m2 * m1)));
		if ((angleChanged2 < 0 && angleChanged < 90) || (angleChanged2 > 0 && angleChanged > 90)) {
			angleChanged = -angleChanged;
		}
		this.angle_out = this.checkAngle(this.angle + angleChanged);

		this.straightenControl.updateAngle(this.angle_out);
	}

	@Override
	public void keyPressed(final KeyEvent arg0) {
		if (arg0.getKeyCode() == KeyEvent.VK_KP_LEFT || arg0.getKeyCode() == KeyEvent.VK_LEFT) {
			this.angle = this.angle - 1;
			this.straightenControl.updateAngle(this.angle);
			this.angle_out = this.checkAngle(this.angle);
			this.renderPreview();
		} else if (arg0.getKeyCode() == KeyEvent.VK_KP_RIGHT || arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
			this.angle = this.angle + 1;
			this.straightenControl.updateAngle(this.angle);
			this.angle_out = this.checkAngle(this.angle);
			this.renderPreview();
		} else if (arg0.getKeyCode() == KeyEvent.VK_1 || arg0.getKeyCode() == KeyEvent.VK_NUMPAD1) {
			this.resizeOption = StraightenFilterPlugin.RESIZE_KEEP_SIZE;
			this.straightenControl.setResizeOption(this.resizeOption);
			this.renderPreview();
		} else if (arg0.getKeyCode() == KeyEvent.VK_2 || arg0.getKeyCode() == KeyEvent.VK_NUMPAD2) {
			this.resizeOption = StraightenFilterPlugin.RESIZE_CROPPING;
			this.straightenControl.setResizeOption(this.resizeOption);
			this.renderPreview();
		} else if (arg0.getKeyCode() == KeyEvent.VK_3 || arg0.getKeyCode() == KeyEvent.VK_NUMPAD3) {
			this.resizeOption = StraightenFilterPlugin.RESIZE_ENLARGE;
			this.straightenControl.setResizeOption(this.resizeOption);
			this.renderPreview();
		}
	}

	@Override
	public void setPreviewImage(final BufferedImage bi) {
		this.inputImage = this.filterManager.cloneBufferedImage(bi);
		this.width = bi.getWidth();
		this.height = bi.getHeight();
		this.renderPreview();
	}

	public double getAngle_out() {
		return this.angle_out;
	}

	public void setAngle_out(final double angle_out) {
		this.angle_out = angle_out;
	}

	public boolean isAntialias() {
		return this.antialias;
	}

	public void setAntialias(final boolean antialias) {
		this.antialias = antialias;
	}

	public boolean isUser_rotating() {
		return this.user_rotating;
	}

	public void setUser_rotating(final boolean user_rotating) {
		this.user_rotating = user_rotating;
	}

	public int getOrientationWidth() {
		return this.orientationWidth;
	}

	public void setOrientationWidth(final int orientationWidth) {
		this.orientationWidth = orientationWidth;
	}

	public int getOrientationHeight() {
		return this.orientationHeight;
	}

	public void setOrientationHeight(final int orientationHeight) {
		this.orientationHeight = orientationHeight;
	}

	public int getStartX() {
		return this.startX;
	}

	public void setStartX(final int startX) {
		this.startX = startX;
	}

	public int getStartY() {
		return this.startY;
	}

	public void setStartY(final int startY) {
		this.startY = startY;
	}

	public int getCurrentX() {
		return this.currentX;
	}

	public void setCurrentX(final int currentX) {
		this.currentX = currentX;
	}

	public int getCurrentY() {
		return this.currentY;
	}

	public void setCurrentY(final int currentY) {
		this.currentY = currentY;
	}

	public int getInitalPanelWidth() {
		return this.initalPanelWidth;
	}

	public void setInitalPanelWidth(final int initalPanelWidth) {
		this.initalPanelWidth = initalPanelWidth;
	}

	public int getInitalPanelHeight() {
		return this.initalPanelHeight;
	}

	public void setInitalPanelHeight(final int initalPanelHeight) {
		this.initalPanelHeight = initalPanelHeight;
	}

	public Color getColor() {
		return this.color;
	}

	public double getAngle() {
		return this.angle;
	}

	public boolean isRandom() {
		return this.random;
	}

	public boolean isShowGrid() {
		return this.showGrid;
	}

	public int getResizeOption() {
		return this.resizeOption;
	}

	public int getGuidelinesVertical() {
		return this.guidelinesVertical;
	}

	public int getGuidelinesHorizontal() {
		return this.guidelinesHorizontal;
	}

	public Dimension getModifiedSize(final Dimension dim, final Map arg1) {
		this.angle_out = this.checkAngle(this.angle_out);
		final int w = dim.width;
		final int h = dim.height;
		final double segment1Width = Math.abs(h * Math.sin(Math.toRadians(this.angle_out)));
		final double segment2Width = Math.abs(w * Math.cos(Math.toRadians(this.angle_out)));
		final double segment1Height = Math.abs(h * Math.cos(Math.toRadians(this.angle_out)));
		final double segment2Height = Math.abs(w * Math.sin(Math.toRadians(this.angle_out)));
		final int newW = (int) (segment1Width + segment2Width);
		final int newH = (int) (segment1Height + segment2Height);

		if (this.resizeOption == StraightenFilterPlugin.RESIZE_CROPPING) {

			int smallerSide = 0;
			// the larger side defines the size of cropped image
			if (dim.width < dim.height) {
				smallerSide = dim.width;
			} else {
				smallerSide = dim.height;
			}

			final double absAngle_out = Math.abs(this.angle_out);
			final double angleA = Math.toRadians(absAngle_out - ((int) (absAngle_out - 1) / 90) * 2 * (absAngle_out - 90));
			final double angleB = Math.atan(w / (double) h);
			final double angleC = angleB - angleA;

			final double diagonal = smallerSide / Math.cos(angleC);

			final int cuttingWidth = (int) (diagonal * Math.sin(angleB));
			final int cuttingHeight = (int) (diagonal * Math.cos(angleB));

			return new Dimension(cuttingWidth, cuttingHeight);

		} else if (this.resizeOption == StraightenFilterPlugin.RESIZE_KEEP_SIZE) {
			return dim;
		} else {
			// RESZIE_ENLARGE or any other, if an error happend
			return new Dimension(newW, newH);
		}
	}

}
